<template>
	<view class="uni-file-picker">
		<view v-if="title" class="uni-file-picker__header">
			<text class="file-title">{{ title }}</text>
			<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
		</view>
		<upload-image v-if="fileMediatype === 'image' && showType === 'grid'" :readonly="readonly" :image-styles="imageStyles"
			:files-list="filesList" :limit="limitLength" :disablePreview="disablePreview" :delIcon="delIcon"
			@uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
			<slot>
				<view class="is-add">
					<view class="icon-add"></view>
					<view class="icon-add rotate"></view>
				</view>
			</slot>
		</upload-image>
		<upload-file v-if="fileMediatype !== 'image' || showType !== 'grid'" :readonly="readonly" :list-styles="listStyles"
			:files-list="filesList" :showType="showType" :delIcon="delIcon" @uploadFiles="uploadFiles" @choose="choose"
			@delFile="delFile">
			<slot><button type="primary" size="mini">选择文件</button></slot>
		</upload-file>
	</view>
</template>

<script>
import {
	chooseAndUploadFile,
	uploadCloudFiles
} from './choose-and-upload-file.js'
import {
	get_file_ext,
	get_extname,
	get_files_and_is_max,
	get_file_info,
	get_file_data
} from './utils.js'
import uploadImage from './upload-image.vue'
import uploadFile from './upload-file.vue'
let fileInput = null
/**
 * FilePicker 文件选择上传
 * @description 文件选择上传组件，可以选择图片、视频等任意文件并上传到当前绑定的服务空间
 * @tutorial https://ext.dcloud.net.cn/plugin?id=4079
 * @property {Object|Array}	value	组件数据，通常用来回显 ,类型由return-type属性决定
 * @property {Boolean}	disabled = [true|false]	组件禁用
 * 	@value true 	禁用
 * 	@value false 	取消禁用
 * @property {Boolean}	readonly = [true|false]	组件只读，不可选择，不显示进度，不显示删除按钮
 * 	@value true 	只读
 * 	@value false 	取消只读
 * @property {String}	return-type = [array|object]	限制 value 格式，当为 object 时 ，组件只能单选，且会覆盖
 * 	@value array	规定 value 属性的类型为数组
 * 	@value object	规定 value 属性的类型为对象
 * @property {Boolean}	disable-preview = [true|false]	禁用图片预览，仅 mode:grid 时生效
 * 	@value true 	禁用图片预览
 * 	@value false 	取消禁用图片预览
 * @property {Boolean}	del-icon = [true|false]	是否显示删除按钮
 * 	@value true 	显示删除按钮
 * 	@value false 	不显示删除按钮
 * @property {Boolean}	auto-upload = [true|false]	是否自动上传，值为true则只触发@select,可自行上传
 * 	@value true 	自动上传
 * 	@value false 	取消自动上传
 * @property {Number|String}	limit	最大选择个数 ，h5 会自动忽略多选的部分
 * @property {String}	title	组件标题，右侧显示上传计数
 * @property {String}	mode = [list|grid]	选择文件后的文件列表样式
 * 	@value list 	列表显示
 * 	@value grid 	宫格显示
 * @property {String}	file-mediatype = [image|video|all]	选择文件类型
 * 	@value image	只选择图片
 * 	@value video	只选择视频
 * 	@value all		选择所有文件
 * @property {Array}	file-extname	选择文件后缀，根据 file-mediatype 属性而不同
 * @property {Object}	list-style	mode:list 时的样式
 * @property {Object}	image-styles	选择文件后缀，根据 file-mediatype 属性而不同
 * @event {Function} select 	选择文件后触发
 * @event {Function} progress 文件上传时触发
 * @event {Function} success 	上传成功触发
 * @event {Function} fail 		上传失败触发
 * @event {Function} delete 	文件从列表移除时触发
 */
export default {
	name: 'uniFilePicker',
	components: {
		uploadImage,
		uploadFile
	},
	emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input'],
	props: {
		// #ifdef VUE3
		modelValue: {
			type: [Array, Object],
			default() {
				return []
			}
		},
		// #endif

		// #ifndef VUE3
		value: {
			type: [Array, Object],
			default() {
				return []
			}
		},
		// #endif

		disabled: {
			type: Boolean,
			default: false
		},
		disablePreview: {
			type: Boolean,
			default: false
		},
		delIcon: {
			type: Boolean,
			default: true
		},
		// 自动上传
		autoUpload: {
			type: Boolean,
			default: true
		},
		// 最大选择个数 ，h5只能限制单选或是多选
		limit: {
			type: [Number, String],
			default: 9
		},
		// 列表样式 grid | list | list-card
		mode: {
			type: String,
			default: 'grid'
		},
		// 选择文件类型  image/video/all
		fileMediatype: {
			type: String,
			default: 'image'
		},
		// 文件类型筛选
		fileExtname: {
			type: [Array, String],
			default() {
				return []
			}
		},
		title: {
			type: String,
			default: ''
		},
		listStyles: {
			type: Object,
			default() {
				return {
					// 是否显示边框
					border: true,
					// 是否显示分隔线
					dividline: true,
					// 线条样式
					borderStyle: {}
				}
			}
		},
		imageStyles: {
			type: Object,
			default() {
				return {
					width: 'auto',
					height: 'auto'
				}
			}
		},
		readonly: {
			type: Boolean,
			default: false
		},
		returnType: {
			type: String,
			default: 'array'
		},
		sizeType: {
			type: Array,
			default() {
				return ['original', 'compressed']
			}
		}
	},
	data() {
		return {
			files: [],
			localValue: []
		}
	},
	watch: {
		// #ifndef VUE3
		value: {
			handler(newVal, oldVal) {
				this.setValue(newVal, oldVal)
			},
			immediate: true
		},
		// #endif
		// #ifdef VUE3
		modelValue: {
			handler(newVal, oldVal) {
				this.setValue(newVal, oldVal)
			},
			immediate: true
		},
		// #endif
	},
	computed: {
		filesList() {
			let files = []
			this.files.forEach(v => {
				files.push(v)
			})
			return files
		},
		showType() {
			if (this.fileMediatype === 'image') {
				return this.mode
			}
			return 'list'
		},
		limitLength() {
			if (this.returnType === 'object') {
				return 1
			}
			if (!this.limit) {
				return 1
			}
			if (this.limit >= 9) {
				return 9
			}
			return this.limit
		}
	},
	created() {
		// TODO 兼容不开通服务空间的情况
		if (!(uniCloud.config && uniCloud.config.provider)) {
			this.noSpace = true
			uniCloud.chooseAndUploadFile = chooseAndUploadFile
		}
		this.form = this.getForm('uniForms')
		this.formItem = this.getForm('uniFormsItem')
		if (this.form && this.formItem) {
			if (this.formItem.name) {
				this.rename = this.formItem.name
				this.form.inputChildrens.push(this)
			}
		}
	},
	methods: {
		/**
		 * 公开用户使用，清空文件
		 * @param {Object} index
		 */
		clearFiles(index) {
			if (index !== 0 && !index) {
				this.files = []
				this.$nextTick(() => {
					this.setEmit()
				})
			} else {
				this.files.splice(index, 1)
			}
			this.$nextTick(() => {
				this.setEmit()
			})
		},
		/**
		 * 公开用户使用，继续上传
		 */
		upload() {
			let files = []
			this.files.forEach((v, index) => {
				if (v.status === 'ready' || v.status === 'error') {
					files.push(Object.assign({}, v))
				}
			})
			this.uploadFiles(files)
		},
		async setValue(newVal, oldVal) {
			const newData = async (v) => {
				const reg = /cloud:\/\/([\w.]+\/?)\S*/
				let url = ''
				if (v.fileID) {
					url = v.fileID
				} else {
					url = v.url
				}
				if (reg.test(url)) {
					v.fileID = url
					v.url = await this.getTempFileURL(url)
				}
				if (v.url) v.path = v.url
				return v
			}
			if (this.returnType === 'object') {
				if (newVal) {
					await newData(newVal)
				} else {
					newVal = {}
				}
			} else {
				if (!newVal) newVal = []
				for (let i = 0; i < newVal.length; i++) {
					let v = newVal[i]
					await newData(v)
				}
			}
			this.localValue = newVal
			if (this.form && this.formItem && !this.is_reset) {
				this.is_reset = false
				this.formItem.setValue(this.localValue)
			}
			let filesData = Object.keys(newVal).length > 0 ? newVal : [];
			this.files = [].concat(filesData)
		},

		/**
		 * 选择文件
		 */
		choose() {

			if (this.disabled) return
			if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
				'array') {
				uni.showToast({
					title: `您最多选择 ${this.limitLength} 个文件`,
					icon: 'none'
				})
				return
			}
			this.chooseFiles()
		},

		/**
		 * 选择文件并上传
		 */
		chooseFiles() {
			const _extname = get_extname(this.fileExtname)
			// 获取后缀
			uniCloud
				.chooseAndUploadFile({
					type: this.fileMediatype,
					compressed: false,
					sizeType: this.sizeType,
					// TODO 如果为空，video 有问题
					extension: _extname.length > 0 ? _extname : undefined,
					count: this.limitLength - this.files.length, //默认9
					onChooseFile: this.chooseFileCallback,
					onUploadProgress: progressEvent => {
						this.setProgress(progressEvent, progressEvent.index)
					}
				})
				.then(result => {
					this.setSuccessAndError(result.tempFiles)
				})
				.catch(err => {
					console.log('选择失败', err)
				})
		},

		/**
		 * 选择文件回调
		 * @param {Object} res
		 */
		async chooseFileCallback(res) {
			const _extname = get_extname(this.fileExtname)
			const is_one = (Number(this.limitLength) === 1 &&
				this.disablePreview &&
				!this.disabled) ||
				this.returnType === 'object'
			// 如果这有一个文件 ，需要清空本地缓存数据
			if (is_one) {
				this.files = []
			}

			let {
				filePaths,
				files
			} = get_files_and_is_max(res, _extname)
			if (!(_extname && _extname.length > 0)) {
				filePaths = res.tempFilePaths
				files = res.tempFiles
			}

			let currentData = []
			for (let i = 0; i < files.length; i++) {
				if (this.limitLength - this.files.length <= 0) break
				files[i].uuid = Date.now()
				let filedata = await get_file_data(files[i], this.fileMediatype)
				filedata.progress = 0
				filedata.status = 'ready'
				this.files.push(filedata)
				currentData.push({
					...filedata,
					file: files[i]
				})
			}
			this.$emit('select', {
				tempFiles: currentData,
				tempFilePaths: filePaths
			})
			res.tempFiles = files
			// 停止自动上传
			if (!this.autoUpload || this.noSpace) {
				res.tempFiles = []
			}
		},

		/**
		 * 批传
		 * @param {Object} e
		 */
		uploadFiles(files) {
			files = [].concat(files)
			uploadCloudFiles.call(this, files, 5, res => {
				this.setProgress(res, res.index, true)
			})
				.then(result => {
					this.setSuccessAndError(result)
				})
				.catch(err => {
					console.log(err)
				})
		},

		/**
		 * 成功或失败
		 */
		async setSuccessAndError(res, fn) {
			let successData = []
			let errorData = []
			let tempFilePath = []
			let errorTempFilePath = []
			for (let i = 0; i < res.length; i++) {
				const item = res[i]
				const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index

				if (index === -1 || !this.files) break
				if (item.errMsg === 'request:fail') {
					this.files[index].url = item.path
					this.files[index].status = 'error'
					this.files[index].errMsg = item.errMsg
					// this.files[index].progress = -1
					errorData.push(this.files[index])
					errorTempFilePath.push(this.files[index].url)
				} else {
					this.files[index].errMsg = ''
					this.files[index].fileID = item.url
					const reg = /cloud:\/\/([\w.]+\/?)\S*/
					if (reg.test(item.url)) {
						this.files[index].url = await this.getTempFileURL(item.url)
					} else {
						this.files[index].url = item.url
					}

					this.files[index].status = 'success'
					this.files[index].progress += 1
					successData.push(this.files[index])
					tempFilePath.push(this.files[index].fileID)
				}
			}

			if (successData.length > 0) {
				this.setEmit()
				// 状态改变返回
				this.$emit('success', {
					tempFiles: this.backObject(successData),
					tempFilePaths: tempFilePath
				})
			}

			if (errorData.length > 0) {
				this.$emit('fail', {
					tempFiles: this.backObject(errorData),
					tempFilePaths: errorTempFilePath
				})
			}
		},

		/**
		 * 获取进度
		 * @param {Object} progressEvent
		 * @param {Object} index
		 * @param {Object} type
		 */
		setProgress(progressEvent, index, type) {
			const fileLenth = this.files.length
			const percentNum = (index / fileLenth) * 100
			const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
			let idx = index
			if (!type) {
				idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
			}
			if (idx === -1 || !this.files[idx]) return
			// fix by mehaotian 100 就会消失，-1 是为了让进度条消失
			this.files[idx].progress = percentCompleted - 1
			// 上传中
			this.$emit('progress', {
				index: idx,
				progress: parseInt(percentCompleted),
				tempFile: this.files[idx]
			})
		},

		/**
		 * 删除文件
		 * @param {Object} index
		 */
		delFile(index) {
			this.$emit('delete', {
				tempFile: this.files[index],
				tempFilePath: this.files[index].url
			})
			this.files.splice(index, 1)
			this.$nextTick(() => {
				this.setEmit()
			})
		},

		/**
		 * 获取文件名和后缀
		 * @param {Object} name
		 */
		getFileExt(name) {
			const last_len = name.lastIndexOf('.')
			const len = name.length
			return {
				name: name.substring(0, last_len),
				ext: name.substring(last_len + 1, len)
			}
		},

		/**
		 * 处理返回事件
		 */
		setEmit() {
			let data = []
			if (this.returnType === 'object') {
				data = this.backObject(this.files)[0]
				this.localValue = data ? data : null
			} else {
				data = this.backObject(this.files)
				if (!this.localValue) {
					this.localValue = []
				}
				this.localValue = [...data]
			}
			// #ifdef VUE3
			this.$emit('update:modelValue', this.localValue)
			// #endif
			// #ifndef VUE3
			this.$emit('input', this.localValue)
			// #endif
		},

		/**
		 * 处理返回参数
		 * @param {Object} files
		 */
		backObject(files) {
			let newFilesData = []
			files.forEach(v => {
				newFilesData.push({
					extname: v.extname,
					fileType: v.fileType,
					image: v.image,
					name: v.name,
					path: v.path,
					size: v.size,
					fileID: v.fileID,
					url: v.url
				})
			})
			return newFilesData
		},
		async getTempFileURL(fileList) {
			fileList = {
				fileList: [].concat(fileList)
			}
			const urls = await uniCloud.getTempFileURL(fileList)
			return urls.fileList[0].tempFileURL || ''
		},
		/**
		 * 获取父元素实例
		 */
		getForm(name = 'uniForms') {
			let parent = this.$parent;
			let parentName = parent.$options.name;
			while (parentName !== name) {
				parent = parent.$parent;
				if (!parent) return false;
				parentName = parent.$options.name;
			}
			return parent;
		}
	}
}
</script>

<style>
.uni-file-picker {
	/* #ifndef APP-NVUE */
	box-sizing: border-box;
	overflow: hidden;
	/* #endif */
}

.uni-file-picker__header {
	padding-top: 5px;
	padding-bottom: 10px;
	/* #ifndef APP-NVUE */
	display: flex;
	/* #endif */
	justify-content: space-between;
}

.file-title {
	font-size: 14px;
	color: #333;
}

.file-count {
	font-size: 14px;
	color: #999;
}

.is-add {
	/* #ifndef APP-NVUE */
	display: flex;
	/* #endif */
	align-items: center;
	justify-content: center;
}

.icon-add {
	width: 50px;
	height: 5px;
	background-color: #f1f1f1;
	border-radius: 2px;
}

.icon-add::before {
	content: '';
}

.rotate {
	position: absolute;
	transform: rotate(90deg);
}
</style>
